/*
 * LEAmDNS.cpp
 *
 *  License (MIT license):
 *    Permission is hereby granted, free of charge, to any person obtaining a copy
 *    of this software and associated documentation files (the "Software"), to deal
 *    in the Software without restriction, including without limitation the rights
 *    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *    copies of the Software, and to permit persons to whom the Software is
 *    furnished to do so, subject to the following conditions:
 *
 *    The above copyright notice and this permission notice shall be included in
 *    all copies or substantial portions of the Software.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 */

#include <Schedule.h>

#include "LEAmDNS_Priv.h"


namespace esp8266 {

/*
 * LEAmDNS
 */
namespace MDNSImplementation {

/**
 * STRINGIZE
 */
#ifndef STRINGIZE
    #define STRINGIZE(x) #x
#endif
#ifndef STRINGIZE_VALUE_OF
    #define STRINGIZE_VALUE_OF(x) STRINGIZE(x)
#endif


/**
 * INTERFACE
 */

/**
 * MDNSResponder::MDNSResponder
 */
MDNSResponder::MDNSResponder(void)
:   m_pServices(0),
    m_pUDPContext(0),
    m_pcHostname(0),
    m_pServiceQueries(0),
    m_fnServiceTxtCallback(0),
#ifdef ENABLE_ESP_MDNS_RESPONDER_PASSIV_MODE
    m_bPassivModeEnabled(true) {
#else
    m_bPassivModeEnabled(false) {
#endif
    
}

/*
 * MDNSResponder::~MDNSResponder
 */
MDNSResponder::~MDNSResponder(void) {
    
    _resetProbeStatus(false);
    _releaseServiceQueries();
    _releaseHostname();
    _releaseUDPContext();
    _releaseServices();
}

/*
 * MDNSResponder::begin
 *
 * Set the host domain (for probing) and install WiFi event handlers for
 * IP assignment and disconnection management. In both cases, the MDNS responder
 * is restarted (reset and restart probe status)
 * Finally the responder is (re)started
 *
 */
bool MDNSResponder::begin(const char* p_pcHostname) {
    
    bool    bResult = false;
    
    if (0 == m_pUDPContext) {
        if (_setHostname(p_pcHostname)) {
            
            m_GotIPHandler = WiFi.onStationModeGotIP([this](const WiFiEventStationModeGotIP& pEvent) {
                (void) pEvent;
                // Ensure that _restart() runs in USER context
                schedule_function([this]() { MDNSResponder::_restart(); });
            });

            m_DisconnectedHandler = WiFi.onStationModeDisconnected([this](const WiFiEventStationModeDisconnected& pEvent) {
                (void) pEvent;
                // Ensure that _restart() runs in USER context
                schedule_function([this]() { MDNSResponder::_restart(); });
                });

            bResult = _restart();
        }
        DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: FAILED for '%s'!\n"), (p_pcHostname ?: "-")); } );
    }
    else {
        DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] begin: Ignoring multiple calls to begin (Ignored host domain: '%s')!\n"), (p_pcHostname ?: "-")););
    }
    return bResult;
}

/*
 * MDNSResponder::begin (LEGACY)
 */
bool MDNSResponder::begin(const char* p_pcHostname,
                          IPAddress p_IPAddress,
                          uint32_t p_u32TTL /*= 120*/) {
    
    (void) p_IPAddress;
    (void) p_u32TTL;
    return begin(p_pcHostname);
}

/*
 * MDNSResponder::close
 *
 * Ends the MDNS responder.
 * Announced services are unannounced (by multicasting a goodbye message)
 *
 */
bool MDNSResponder::close(void) {
    
    m_GotIPHandler.reset();			// reset WiFi event callbacks.
    m_DisconnectedHandler.reset();

    _announce(false, true);
    _resetProbeStatus(false);   // Stop probing

    _releaseServiceQueries();
    _releaseUDPContext();
    _releaseHostname();
    
    return true;
}

/*
 * MDNSResponder::end
 *
 * Ends the MDNS responder.
 * for compatibility with esp32
 *
 */

bool MDNSResponder::end(void) {
    return close();
}

/*
 * MDNSResponder::setHostname
 *
 * Replaces the current hostname and restarts probing.
 * For services without own instance name (when the host name was used a instance
 * name), the instance names are replaced also (and the probing is restarted).
 *
 */
bool MDNSResponder::setHostname(const char* p_pcHostname) {
    
    bool    bResult = false;

    if (_setHostname(p_pcHostname)) {
        m_HostProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart;

        // Replace 'auto-set' service names
        bResult = true;
        for (stcMDNSService* pService=m_pServices; ((bResult) && (pService)); pService=pService->m_pNext) {
            if (pService->m_bAutoName) {
                bResult = pService->setName(p_pcHostname);
                pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart;
            }
        }
    }
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] setHostname: FAILED for '%s'!\n"), (p_pcHostname ?: "-")); } );
    return bResult;
}

/*
 * MDNSResponder::setHostname (LEGACY)
 */
bool MDNSResponder::setHostname(String p_strHostname) {
    
    return setHostname(p_strHostname.c_str());
}


/*
 * SERVICES
 */
 
/*
 * MDNSResponder::addService
 *
 * Add service; using hostname if no name is explicitly provided for the service
 * The usual '_' underline, which is prepended to service and protocol, eg. _http,
 * may be given. If not, it is added automatically.
 *
 */
MDNSResponder::hMDNSService MDNSResponder::addService(const char* p_pcName,
                                                      const char* p_pcService,
                                                      const char* p_pcProtocol,
                                                      uint16_t p_u16Port) {

    hMDNSService    hResult = 0;
    
    if (((!p_pcName) ||                                                     // NO name OR
         (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcName))) &&           // Fitting name
        (p_pcService) &&
        (MDNS_SERVICE_NAME_LENGTH >= os_strlen(p_pcService)) &&
        (p_pcProtocol) &&
        ((MDNS_SERVICE_PROTOCOL_LENGTH - 1) != os_strlen(p_pcProtocol)) &&
        (p_u16Port)) {
        
        if (!_findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol)) { // Not already used
            if (0 != (hResult = (hMDNSService)_allocService(p_pcName, p_pcService, p_pcProtocol, p_u16Port))) {
                
                // Start probing
                ((stcMDNSService*)hResult)->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart;
          }
        }
    }   // else: bad arguments
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addService: %s to add '%s.%s.%s'!\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcName ?: "-"), p_pcService, p_pcProtocol); );
    DEBUG_EX_ERR(if (!hResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addService: FAILED to add '%s.%s.%s'!\n"), (p_pcName ?: "-"), p_pcService, p_pcProtocol); } );
    return hResult;
}

/*
 * MDNSResponder::removeService
 *
 * Unanounce a service (by sending a goodbye message) and remove it
 * from the MDNS responder
 *
 */
bool MDNSResponder::removeService(const MDNSResponder::hMDNSService p_hService) {
    
    stcMDNSService* pService = 0;
    bool    bResult = (((pService = _findService(p_hService))) &&
                       (_announceService(*pService, false)) &&
                       (_releaseService(pService)));
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] removeService: FAILED!\n")); } );
    return bResult;
}

/*
 * MDNSResponder::removeService
 */
bool MDNSResponder::removeService(const char* p_pcName,
                                  const char* p_pcService,
                                  const char* p_pcProtocol) {
    
    return removeService((hMDNSService)_findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol));
}

/*
 * MDNSResponder::addService (LEGACY)
 */
bool MDNSResponder::addService(String p_strService,
                               String p_strProtocol,
                               uint16_t p_u16Port) {
    
    return (0 != addService(m_pcHostname, p_strService.c_str(), p_strProtocol.c_str(), p_u16Port));
}

/*
 * MDNSResponder::setServiceName
 */
bool MDNSResponder::setServiceName(const MDNSResponder::hMDNSService p_hService,
                                   const char* p_pcInstanceName) {

    stcMDNSService* pService = 0;
    bool    bResult = (((!p_pcInstanceName) ||
                        (MDNS_DOMAIN_LABEL_MAXLENGTH >= os_strlen(p_pcInstanceName))) &&
                       ((pService = _findService(p_hService))) &&
                       (pService->setName(p_pcInstanceName)) &&
                       ((pService->m_ProbeInformation.m_ProbingStatus = ProbingStatus_ReadyToStart)));
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] setServiceName: FAILED for '%s'!\n"), (p_pcInstanceName ?: "-")); } );
    return bResult;
}

/*
 * SERVICE TXT
 */

/*
 * MDNSResponder::addServiceTxt
 *
 * Add a static service TXT item ('Key'='Value') to a service.
 *
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     const char* p_pcValue) {
    
    hMDNSTxt    hTxt = 0;
    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        hTxt = (hMDNSTxt)_addServiceTxt(pService, p_pcKey, p_pcValue, false);
    }
    DEBUG_EX_ERR(if (!hTxt) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addServiceTxt: FAILED for '%s=%s'!\n"), (p_pcKey ?: "-"), (p_pcValue ?: "-")); } );
    return hTxt;
}

/*
 * MDNSResponder::addServiceTxt (uint32_t)
 *
 * Formats: http://www.cplusplus.com/reference/cstdio/printf/
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     uint32_t p_u32Value) {
    char    acBuffer[32];   *acBuffer = 0;
    sprintf(acBuffer, "%u", p_u32Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addServiceTxt (uint16_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     uint16_t p_u16Value) {
    char    acBuffer[16];   *acBuffer = 0;
    sprintf(acBuffer, "%hu", p_u16Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addServiceTxt (uint8_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     uint8_t p_u8Value) {
    char    acBuffer[8];    *acBuffer = 0;
    sprintf(acBuffer, "%hhu", p_u8Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addServiceTxt (int32_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     int32_t p_i32Value) {
    char    acBuffer[32];   *acBuffer = 0;
    sprintf(acBuffer, "%i", p_i32Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addServiceTxt (int16_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     int16_t p_i16Value) {
    char    acBuffer[16];   *acBuffer = 0;
    sprintf(acBuffer, "%hi", p_i16Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addServiceTxt (int8_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                                     const char* p_pcKey,
                                                     int8_t p_i8Value) {
    char    acBuffer[8];    *acBuffer = 0;
    sprintf(acBuffer, "%hhi", p_i8Value);

    return addServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::removeServiceTxt
 *
 * Remove a static service TXT item from a service.
 */
bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                     const MDNSResponder::hMDNSTxt p_hTxt) {

    bool    bResult = false;
    
    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        stcMDNSServiceTxt*  pTxt = _findServiceTxt(pService, p_hTxt);
        if (pTxt) {
            bResult = _releaseServiceTxt(pService, pTxt);
        }
    }
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] removeServiceTxt: FAILED!\n")); } );
    return bResult;
}

/*
 * MDNSResponder::removeServiceTxt
 */
bool MDNSResponder::removeServiceTxt(const MDNSResponder::hMDNSService p_hService,
                                     const char* p_pcKey) {

    bool    bResult = false;
    
    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        stcMDNSServiceTxt*  pTxt = _findServiceTxt(pService, p_pcKey);
        if (pTxt) {
            bResult = _releaseServiceTxt(pService, pTxt);
        }
    }
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] removeServiceTxt: FAILED for '%s'!\n"), (p_pcKey ?: "-")); } );
    return bResult;
}

/*
 * MDNSResponder::removeServiceTxt
 */
bool MDNSResponder::removeServiceTxt(const char* p_pcName,
                                     const char* p_pcService,
                                     const char* p_pcProtocol,
                                     const char* p_pcKey) {

    bool    bResult = false;
    
    stcMDNSService* pService = _findService((p_pcName ?: m_pcHostname), p_pcService, p_pcProtocol);
    if (pService) {
        stcMDNSServiceTxt*  pTxt = _findServiceTxt(pService, p_pcKey);
        if (pTxt) {
            bResult = _releaseServiceTxt(pService, pTxt);
        }
    }
    return bResult;
}

/*
 * MDNSResponder::addServiceTxt (LEGACY)
 */
bool MDNSResponder::addServiceTxt(const char* p_pcService,
                                  const char* p_pcProtocol,
                                  const char* p_pcKey,
                                  const char* p_pcValue) {
    
    return (0 != _addServiceTxt(_findService(m_pcHostname, p_pcService, p_pcProtocol), p_pcKey, p_pcValue, false));
}

/*
 * MDNSResponder::addServiceTxt (LEGACY)
 */
bool MDNSResponder::addServiceTxt(String p_strService,
                                  String p_strProtocol,
                                  String p_strKey,
                                  String p_strValue) {
    
    return (0 != _addServiceTxt(_findService(m_pcHostname, p_strService.c_str(), p_strProtocol.c_str()), p_strKey.c_str(), p_strValue.c_str(), false));
}

/*
 * MDNSResponder::setDynamicServiceTxtCallback (global)
 *
 * Set a global callback for dynamic service TXT items. The callback is called, whenever
 * service TXT items are needed.
 *
 */
bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) {
    
    m_fnServiceTxtCallback = p_fnCallback;
    
    return true;
}

/*
 * MDNSResponder::setDynamicServiceTxtCallback (service specific)
 *
 * Set a service specific callback for dynamic service TXT items. The callback is called, whenever
 * service TXT items are needed for the given service.
 *
 */
bool MDNSResponder::setDynamicServiceTxtCallback(MDNSResponder::hMDNSService p_hService,
                                                 MDNSResponder::MDNSDynamicServiceTxtCallbackFunc p_fnCallback) {

    bool    bResult = false;
    
    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        pService->m_fnTxtCallback = p_fnCallback;
        
        bResult = true;
    }   
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] setDynamicServiceTxtCallback: FAILED!\n")); } );
    return bResult;
}

/*
 * MDNSResponder::addDynamicServiceTxt
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            const char* p_pcValue) {
    //DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addDynamicServiceTxt (%s=%s)\n"), p_pcKey, p_pcValue););
    
    hMDNSTxt        hTxt = 0;

    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        hTxt = _addServiceTxt(pService, p_pcKey, p_pcValue, true);
    }
    DEBUG_EX_ERR(if (!hTxt) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] addDynamicServiceTxt: FAILED for '%s=%s'!\n"), (p_pcKey ?: "-"), (p_pcValue ?: "-")); } );
    return hTxt;
}

/*
 * MDNSResponder::addDynamicServiceTxt (uint32_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            uint32_t p_u32Value) {

    char    acBuffer[32];   *acBuffer = 0;
    sprintf(acBuffer, "%u", p_u32Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addDynamicServiceTxt (uint16_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            uint16_t p_u16Value) {

    char    acBuffer[16];   *acBuffer = 0;
    sprintf(acBuffer, "%hu", p_u16Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addDynamicServiceTxt (uint8_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            uint8_t p_u8Value) {

    char    acBuffer[8];    *acBuffer = 0;
    sprintf(acBuffer, "%hhu", p_u8Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addDynamicServiceTxt (int32_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            int32_t p_i32Value) {

    char    acBuffer[32];   *acBuffer = 0;
    sprintf(acBuffer, "%i", p_i32Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addDynamicServiceTxt (int16_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            int16_t p_i16Value) {

    char    acBuffer[16];   *acBuffer = 0;
    sprintf(acBuffer, "%hi", p_i16Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}

/*
 * MDNSResponder::addDynamicServiceTxt (int8_t)
 */
MDNSResponder::hMDNSTxt MDNSResponder::addDynamicServiceTxt(MDNSResponder::hMDNSService p_hService,
                                                            const char* p_pcKey,
                                                            int8_t p_i8Value) {

    char    acBuffer[8];    *acBuffer = 0;
    sprintf(acBuffer, "%hhi", p_i8Value);

    return addDynamicServiceTxt(p_hService, p_pcKey, acBuffer);
}


/**
 * STATIC SERVICE QUERY (LEGACY)
 */
 
/*
 * MDNSResponder::queryService
 *
 * Perform a (blocking) static service query.
 * The arrived answers can be queried by calling:
 *  - answerHostname (or 'hostname')
 *  - answerIP (or 'IP')
 *  - answerPort (or 'port')
 *
 */
uint32_t MDNSResponder::queryService(const char* p_pcService,
                                     const char* p_pcProtocol,
                                     const uint16_t p_u16Timeout /*= MDNS_QUERYSERVICES_WAIT_TIME*/) {
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] queryService '%s.%s'\n"), p_pcService, p_pcProtocol););
    
    uint32_t    u32Result = 0;

    stcMDNSServiceQuery*    pServiceQuery = 0;
    if ((p_pcService) &&
        (os_strlen(p_pcService)) &&
        (p_pcProtocol) &&
        (os_strlen(p_pcProtocol)) &&
        (p_u16Timeout) &&
        (_removeLegacyServiceQuery()) &&
        ((pServiceQuery = _allocServiceQuery())) &&
        (_buildDomainForService(p_pcService, p_pcProtocol, pServiceQuery->m_ServiceTypeDomain))) {
        
        pServiceQuery->m_bLegacyQuery = true;
        
        if (_sendMDNSServiceQuery(*pServiceQuery)) {
            // Wait for answers to arrive
            DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] queryService: Waiting %u ms for answers...\n"), p_u16Timeout););
            delay(p_u16Timeout);

            // All answers should have arrived by now -> stop adding new answers
            pServiceQuery->m_bAwaitingAnswers = false;
            u32Result = pServiceQuery->answerCount();
        }
        else {  // FAILED to send query
            _removeServiceQuery(pServiceQuery);
        }
    }
    else {
        DEBUG_EX_ERR(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] queryService: INVALID input data!\n"), p_pcService, p_pcProtocol););
    }
    return u32Result;
}

/*
 * MDNSResponder::removeQuery
 *
 * Remove the last static service query (and all answers).
 *
 */
bool MDNSResponder::removeQuery(void) {
    
    return _removeLegacyServiceQuery();
}

/*
 * MDNSResponder::queryService (LEGACY)
 */
uint32_t MDNSResponder::queryService(String p_strService,
                                     String p_strProtocol) {
    
    return queryService(p_strService.c_str(), p_strProtocol.c_str());
}

/*
 * MDNSResponder::answerHostname
 */
const char* MDNSResponder::answerHostname(const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findLegacyServiceQuery();
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);

    if ((pSQAnswer) &&
        (pSQAnswer->m_HostDomain.m_u16NameLength) &&
        (!pSQAnswer->m_pcHostDomain)) {

        char*   pcHostDomain = pSQAnswer->allocHostDomain(pSQAnswer->m_HostDomain.c_strLength());
        if (pcHostDomain) {
            pSQAnswer->m_HostDomain.c_str(pcHostDomain);
        }
    }
    return (pSQAnswer ? pSQAnswer->m_pcHostDomain : 0);
}

#ifdef MDNS_IP4_SUPPORT
    /*
     * MDNSResponder::answerIP
     */
    IPAddress MDNSResponder::answerIP(const uint32_t p_u32AnswerIndex) {

        const stcMDNSServiceQuery*                              pServiceQuery = _findLegacyServiceQuery();
        const stcMDNSServiceQuery::stcAnswer*                   pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        const stcMDNSServiceQuery::stcAnswer::stcIP4Address*    pIP4Address = (((pSQAnswer) && (pSQAnswer->m_pIP4Addresses)) ? pSQAnswer->IP4AddressAtIndex(0) : 0);
        return (pIP4Address ? pIP4Address->m_IPAddress : IPAddress());
    }
#endif

#ifdef MDNS_IP6_SUPPORT
    /*
     * MDNSResponder::answerIP6
     */
    IPAddress MDNSResponder::answerIP6(const uint32_t p_u32AnswerIndex) {

        const stcMDNSServiceQuery*                              pServiceQuery = _findLegacyServiceQuery();
        const stcMDNSServiceQuery::stcAnswer*                   pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        const stcMDNSServiceQuery::stcAnswer::stcIP6Address*    pIP6Address = (((pSQAnswer) && (pSQAnswer->m_pIP6Addresses)) ? pSQAnswer->IP6AddressAtIndex(0) : 0);
        return (pIP6Address ? pIP6Address->m_IPAddress : IP6Address());
    }
#endif

/*
 * MDNSResponder::answerPort
 */
uint16_t MDNSResponder::answerPort(const uint32_t p_u32AnswerIndex) {

    const stcMDNSServiceQuery*              pServiceQuery = _findLegacyServiceQuery();
    const stcMDNSServiceQuery::stcAnswer*   pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    return (pSQAnswer ? pSQAnswer->m_u16Port : 0);
}

/*
 * MDNSResponder::hostname (LEGACY)
 */
String MDNSResponder::hostname(const uint32_t p_u32AnswerIndex) {
    
    return String(answerHostname(p_u32AnswerIndex));
}

/*
 * MDNSResponder::IP (LEGACY)
 */
IPAddress MDNSResponder::IP(const uint32_t p_u32AnswerIndex) {
    
    return answerIP(p_u32AnswerIndex);
}

/*
 * MDNSResponder::port (LEGACY)
 */
uint16_t MDNSResponder::port(const uint32_t p_u32AnswerIndex) {
    
    return answerPort(p_u32AnswerIndex);
}


/**
 * DYNAMIC SERVICE QUERY
 */

/*
 * MDNSResponder::installServiceQuery
 *
 * Add a dynamic service query and a corresponding callback to the MDNS responder.
 * The callback will be called for every answer update.
 * The answers can also be queried by calling:
 *  - answerServiceDomain
 *  - answerHostDomain
 *  - answerIP4Address/answerIP6Address
 *  - answerPort
 *  - answerTxts
 *
 */
MDNSResponder::hMDNSServiceQuery MDNSResponder::installServiceQuery(const char* p_pcService,
                                                                    const char* p_pcProtocol,
                                                                    MDNSResponder::MDNSServiceQueryCallbackFunc p_fnCallback) {
    hMDNSServiceQuery       hResult = 0;
    
    stcMDNSServiceQuery*    pServiceQuery = 0;
    if ((p_pcService) &&
        (os_strlen(p_pcService)) &&
        (p_pcProtocol) &&
        (os_strlen(p_pcProtocol)) &&
        (p_fnCallback) &&
        ((pServiceQuery = _allocServiceQuery())) &&
        (_buildDomainForService(p_pcService, p_pcProtocol, pServiceQuery->m_ServiceTypeDomain))) {

        pServiceQuery->m_fnCallback = p_fnCallback;
        pServiceQuery->m_bLegacyQuery = false;
        
        if (_sendMDNSServiceQuery(*pServiceQuery)) {
            pServiceQuery->m_u8SentCount = 1;
            pServiceQuery->m_ResendTimeout.reset(MDNS_DYNAMIC_QUERY_RESEND_DELAY);

            hResult = (hMDNSServiceQuery)pServiceQuery;
        }
        else {
            _removeServiceQuery(pServiceQuery);
        }
    }
    DEBUG_EX_INFO(DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: %s for '%s.%s'!\n\n"), (hResult ? "Succeeded" : "FAILED"), (p_pcService ?: "-"), (p_pcProtocol ?: "-")););
    DEBUG_EX_ERR(if (!hResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] installServiceQuery: FAILED for '%s.%s'!\n\n"), (p_pcService ?: "-"), (p_pcProtocol ?: "-")); } );
    return hResult;
}

/*
 * MDNSResponder::removeServiceQuery
 *
 * Remove a dynamic service query (and all collected answers) from the MDNS responder
 *
 */
bool MDNSResponder::removeServiceQuery(MDNSResponder::hMDNSServiceQuery p_hServiceQuery) {
    
    stcMDNSServiceQuery*    pServiceQuery = 0;
    bool    bResult = (((pServiceQuery = _findServiceQuery(p_hServiceQuery))) &&
                       (_removeServiceQuery(pServiceQuery)));
    DEBUG_EX_ERR(if (!bResult) { DEBUG_OUTPUT.printf_P(PSTR("[MDNSResponder] removeServiceQuery: FAILED!\n")); } );
    return bResult;
}

/*
 * MDNSResponder::answerCount
 */
uint32_t MDNSResponder::answerCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) {
    
    stcMDNSServiceQuery*    pServiceQuery = _findServiceQuery(p_hServiceQuery);
    return (pServiceQuery ? pServiceQuery->answerCount() : 0);
}

std::vector<MDNSResponder::MDNSServiceInfo>  MDNSResponder::answerInfo (const MDNSResponder::hMDNSServiceQuery p_hServiceQuery) {
    std::vector<MDNSResponder::MDNSServiceInfo> tempVector;
    for (uint32_t i=0;i<answerCount(p_hServiceQuery);i++)
    {
        tempVector.emplace_back(*this,p_hServiceQuery,i);
    }
    return tempVector;
}

/*
 * MDNSResponder::answerServiceDomain
 *
 * Returns the domain for the given service.
 * If not already existing, the string is allocated, filled and attached to the answer.
 *
 */
const char* MDNSResponder::answerServiceDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                               const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    // Fill m_pcServiceDomain (if not already done)
    if ((pSQAnswer) &&
        (pSQAnswer->m_ServiceDomain.m_u16NameLength) &&
        (!pSQAnswer->m_pcServiceDomain)) {
        
        pSQAnswer->m_pcServiceDomain = pSQAnswer->allocServiceDomain(pSQAnswer->m_ServiceDomain.c_strLength());
        if (pSQAnswer->m_pcServiceDomain) {
            pSQAnswer->m_ServiceDomain.c_str(pSQAnswer->m_pcServiceDomain);
        }
    }
    return (pSQAnswer ? pSQAnswer->m_pcServiceDomain : 0);
}

/*
 * MDNSResponder::hasAnswerHostDomain
 */
bool MDNSResponder::hasAnswerHostDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                        const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    return ((pSQAnswer) &&
            (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostDomainAndPort));
}
                                            
/*
 * MDNSResponder::answerHostDomain
 *
 * Returns the host domain for the given service.
 * If not already existing, the string is allocated, filled and attached to the answer.
 * 
 */ 
const char* MDNSResponder::answerHostDomain(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                            const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    // Fill m_pcHostDomain (if not already done)
    if ((pSQAnswer) &&
        (pSQAnswer->m_HostDomain.m_u16NameLength) &&
        (!pSQAnswer->m_pcHostDomain)) {
        
        pSQAnswer->m_pcHostDomain = pSQAnswer->allocHostDomain(pSQAnswer->m_HostDomain.c_strLength());
        if (pSQAnswer->m_pcHostDomain) {
            pSQAnswer->m_HostDomain.c_str(pSQAnswer->m_pcHostDomain);
        }
    }
    return (pSQAnswer ? pSQAnswer->m_pcHostDomain : 0);
}

#ifdef MDNS_IP4_SUPPORT
    /*
     * MDNSResponder::hasAnswerIP4Address
     */
    bool MDNSResponder::hasAnswerIP4Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                            const uint32_t p_u32AnswerIndex) {

        stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        return ((pSQAnswer) &&
                (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_IP4Address));
    }
    
    /*
     * MDNSResponder::answerIP4AddressCount
     */
    uint32_t MDNSResponder::answerIP4AddressCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                                  const uint32_t p_u32AnswerIndex) {
        
        stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        return (pSQAnswer ? pSQAnswer->IP4AddressCount() : 0);
    }

    /*
     * MDNSResponder::answerIP4Address
     */
    IPAddress MDNSResponder::answerIP4Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                              const uint32_t p_u32AnswerIndex,
                                              const uint32_t p_u32AddressIndex) {

        stcMDNSServiceQuery*                            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer*                 pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        stcMDNSServiceQuery::stcAnswer::stcIP4Address*  pIP4Address = (pSQAnswer ? pSQAnswer->IP4AddressAtIndex(p_u32AddressIndex) : 0);
        return (pIP4Address ? pIP4Address->m_IPAddress : IPAddress());
    }
#endif

#ifdef MDNS_IP6_SUPPORT
    /*
     * MDNSResponder::hasAnswerIP6Address
     */
    bool MDNSResponder::hasAnswerIP6Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                            const uint32_t p_u32AnswerIndex) {

        stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        return ((pSQAnswer) &&
                (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostIP6Address));
    }
    
    /*
     * MDNSResponder::answerIP6AddressCount
     */
    uint32_t MDNSResponder::answerIP6AddressCount(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                                  const uint32_t p_u32AnswerIndex) {
        
        stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        return (pSQAnswer ? pSQAnswer->IP6AddressCount() : 0);
    }

    /*
     * MDNSResponder::answerIP6Address
     */
    IPAddress MDNSResponder::answerIP6Address(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                              const uint32_t p_u32AnswerIndex,
                                              const uint32_t p_u32AddressIndex) {

        stcMDNSServiceQuery*                            pServiceQuery = _findServiceQuery(p_hServiceQuery);
        stcMDNSServiceQuery::stcAnswer*                 pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
        stcMDNSServiceQuery::stcAnswer::stcIP6Address*  pIP6Address = (pSQAnswer ? pSQAnswer->IP6AddressAtIndex(p_u32AddressIndex) : 0);
        return (pIP6Address ? pIP6Address->m_IPAddress : IPAddress());
    }
#endif

/*
 * MDNSResponder::hasAnswerPort
 */
bool MDNSResponder::hasAnswerPort(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                  const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    return ((pSQAnswer) &&
            (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_HostDomainAndPort));
}

/*
 * MDNSResponder::answerPort
 */
uint16_t MDNSResponder::answerPort(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                   const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    return (pSQAnswer ? pSQAnswer->m_u16Port : 0);
}

/*
 * MDNSResponder::hasAnswerTxts
 */
bool MDNSResponder::hasAnswerTxts(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                  const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    return ((pSQAnswer) &&
            (pSQAnswer->m_u32ContentFlags & ServiceQueryAnswerType_Txts));
}

/*
 * MDNSResponder::answerTxts
 *
 * Returns all TXT items for the given service as a ';'-separated string.
 * If not already existing; the string is alloced, filled and attached to the answer.
 *
 */
const char* MDNSResponder::answerTxts(const MDNSResponder::hMDNSServiceQuery p_hServiceQuery,
                                      const uint32_t p_u32AnswerIndex) {

    stcMDNSServiceQuery*            pServiceQuery = _findServiceQuery(p_hServiceQuery);
    stcMDNSServiceQuery::stcAnswer* pSQAnswer = (pServiceQuery ? pServiceQuery->answerAtIndex(p_u32AnswerIndex) : 0);
    // Fill m_pcTxts (if not already done)
    if ((pSQAnswer) &&
        (pSQAnswer->m_Txts.m_pTxts) &&
        (!pSQAnswer->m_pcTxts)) {

        pSQAnswer->m_pcTxts = pSQAnswer->allocTxts(pSQAnswer->m_Txts.c_strLength());
        if (pSQAnswer->m_pcTxts) {
            pSQAnswer->m_Txts.c_str(pSQAnswer->m_pcTxts);
        }
    }
    return (pSQAnswer ? pSQAnswer->m_pcTxts : 0);
}

/*
 * PROBING
 */

/*
 * MDNSResponder::setProbeResultCallback
 *
 * Set a global callback for probe results. The callback is called, when probing
 * for the host domain (or a service domain, without specific probe result callback)
 * failes or succeedes.
 * In the case of failure, the domain name should be changed via 'setHostname' or 'setServiceName'.
 * When succeeded, the host or service domain will be announced by the MDNS responder.
 *
 */
bool MDNSResponder::setHostProbeResultCallback(MDNSResponder::MDNSHostProbeFn p_fnCallback) {

    m_HostProbeInformation.m_fnHostProbeResultCallback = p_fnCallback;
    
    return true;
}

bool MDNSResponder::setHostProbeResultCallback(MDNSHostProbeFn1 pfn) {
    using namespace std::placeholders;
    return setHostProbeResultCallback([resp=std::ref(*this), pfn](const char* p_pcDomainName, bool p_bProbeResult) { pfn(resp, p_pcDomainName, p_bProbeResult); });
}

/*
 * MDNSResponder::setServiceProbeResultCallback
 *
 * Set a service specific callback for probe results. The callback is called, when probing
 * for the service domain failes or succeedes.
 * In the case of failure, the service name should be changed via 'setServiceName'.
 * When succeeded, the service domain will be announced by the MDNS responder.
 *
 */
bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService,
                                                  MDNSResponder::MDNSServiceProbeFn p_fnCallback) {

    bool    bResult = false;

    stcMDNSService* pService = _findService(p_hService);
    if (pService) {
        pService->m_ProbeInformation.m_fnServiceProbeResultCallback = p_fnCallback;

        bResult = true;
    }
    return bResult;
}

bool MDNSResponder::setServiceProbeResultCallback(const MDNSResponder::hMDNSService p_hService,
                                                  MDNSResponder::MDNSServiceProbeFn1 p_fnCallback) {
    using namespace std::placeholders;
    return setServiceProbeResultCallback(p_hService, [resp=std::ref(*this), p_fnCallback](const char* p_pcServiceName, const hMDNSService p_hMDNSService, bool p_bProbeResult) {
        p_fnCallback(resp, p_pcServiceName, p_hMDNSService, p_bProbeResult);
    });
}


/*
 * MISC
 */

/*
 * MDNSResponder::notifyAPChange
 *
 * Should be called, whenever the AP for the MDNS responder changes.
 * A bit of this is caught by the event callbacks installed in the constructor.
 *
 */
bool MDNSResponder::notifyAPChange(void) {
    
    return _restart();
}

/*
 * MDNSResponder::update
 *
 * Should be called in every 'loop'.
 *
 */
bool MDNSResponder::update(void) {
    
    if (m_bPassivModeEnabled) {
        m_bPassivModeEnabled = false;
    }
    return _process(true);
}

/*
 * MDNSResponder::announce
 *
 * Should be called, if the 'configuration' changes. Mainly this will be changes in the TXT items...
 */
bool MDNSResponder::announce(void) {
    
    return (_announce(true, true));
}       

/*
 * MDNSResponder::enableArduino
 *
 * Enable the OTA update service.
 *
 */
MDNSResponder::hMDNSService MDNSResponder::enableArduino(uint16_t p_u16Port,
                                                         bool p_bAuthUpload /*= false*/) {
    
    hMDNSService    hService = addService(0, "arduino", "tcp", p_u16Port);
    if (hService) {
        if ((!addServiceTxt(hService, "tcp_check", "no")) ||
           (!addServiceTxt(hService, "ssh_upload", "no")) ||
          (!addServiceTxt(hService, "board", STRINGIZE_VALUE_OF(ARDUINO_BOARD))) ||
          (!addServiceTxt(hService, "auth_upload", (p_bAuthUpload) ? "yes" : "no"))) {
           
           removeService(hService);
           hService = 0;
      }
    }
    return hService;
}


} //namespace MDNSImplementation

} //namespace esp8266


